﻿using UnityEngine;
using System;

namespace Obi
{

    /**
	 * Implements common functionality for ObiCollider and ObiCollider2D.
	 */
    public abstract class ObiColliderBase : MonoBehaviour
    {

        [SerializeProperty("Thickness")]
        [SerializeField] private float thickness = 0;

        [SerializeProperty("Inverted")]
        [SerializeField] private bool inverted = false;

        [SerializeProperty("CollisionMaterial")]
        [SerializeField] private ObiCollisionMaterial material;

        [SerializeField] private int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);

        public ObiCollisionMaterial CollisionMaterial
        {
            set
            {
                if (material != value)
                {
                    material = value;
                    ForceUpdate();
                }
            }
            get { return material; }
        }

        public int Filter
        {
            set
            {
                if (filter != value)
                {
                    filter = value;
                    ForceUpdate();
                }
            }
            get { return filter; }
        }

        public float Thickness
        {
            set
            {
                if (!Mathf.Approximately(thickness, value))
                {
                    thickness = value;
                    ForceUpdate();
                }
            }
            get { return thickness; }
        }

        public bool Inverted
        {
            set
            {
                if (inverted != value)
                {
                    inverted = value;
                    ForceUpdate();
                }
            }
            get { return inverted; }
        }

        public ObiShapeTracker Tracker
        {
            get { return tracker; }
        }

        public ObiColliderHandle Handle
        {
            get
            {
                if (shapeHandle == null)
                    FindSourceCollider();
                return shapeHandle;
            }
        }

        public ObiForceZone ForceZone 
        {
            get; set;
        }

        public ObiRigidbodyBase Rigidbody
        {
            get { return obiRigidbody; }
        }

        protected ObiColliderHandle shapeHandle;
        protected ObiRigidbodyBase obiRigidbody;
        protected bool wasUnityColliderEnabled = true;

        protected ObiShapeTracker tracker;                               /**< tracker object used to determine when to update the collider's shape*/

        /**
		 * Creates an OniColliderTracker of the appropiate type.
   		 */
        protected abstract void CreateTracker();

        protected abstract Component GetUnityCollider(ref bool enabled);

        protected abstract void FindSourceCollider();

        protected void CreateRigidbody()
        {

            obiRigidbody = null;

            // find the first rigidbody up our hierarchy:
            Rigidbody rb = GetComponentInParent<Rigidbody>();
            Rigidbody2D rb2D = GetComponentInParent<Rigidbody2D>();

            // if we have an rigidbody above us, see if it has a ObiRigidbody component and add one if it doesn't:
            if (rb != null)
            {

                obiRigidbody = rb.GetComponent<ObiRigidbody>();

                if (obiRigidbody == null)
                    obiRigidbody = rb.gameObject.AddComponent<ObiRigidbody>();

            }
            else if (rb2D != null)
            {

                obiRigidbody = rb2D.GetComponent<ObiRigidbody2D>();

                if (obiRigidbody == null)
                    obiRigidbody = rb2D.gameObject.AddComponent<ObiRigidbody2D>();

            }

        }

        private void OnTransformParentChanged()
        {
            CreateRigidbody();
        }

        protected void AddCollider()
        {
            Component unityCollider = GetUnityCollider(ref wasUnityColliderEnabled);

            if (unityCollider != null && (shapeHandle == null || !shapeHandle.isValid))
            {
                shapeHandle = ObiColliderWorld.GetInstance().CreateCollider();
                shapeHandle.owner = this;

                // Create shape tracker:
                CreateTracker();

                // Create rigidbody if necessary, and link ourselves to it:
                CreateRigidbody();
            }
        }

        protected void RemoveCollider()
        {
            ObiColliderWorld.GetInstance().DestroyCollider(shapeHandle);

            // Destroy shape tracker:
            if (tracker != null)
            {
                tracker.Destroy();
                tracker = null;
            }
        }

        /** 
		 * Flags the collider as needing to be updated from now on. If the object it's on has any editor static flag enabled,
         * the collider will be again flagged as *not* needing to be updated after its next update.
		 */
        public void ForceUpdate()
        {
            ObiColliderWorld.GetInstance().MarkColliderAsNeedingUpdate(shapeHandle);
        }

        /** 
		 * Check if the collider transform or its shape have changed any relevant property, and update their Oni counterparts.
		 */
        public void UpdateIfNeeded()
        {
            bool unityColliderEnabled = false;
            Component unityCollider = GetUnityCollider(ref unityColliderEnabled);

            if (unityCollider != null)
            {
                // Only if this object is not static:
                if (tracker != null) //&& needsUpdate)
                {
                    tracker.UpdateIfNeeded();
                }

                // check isStatic, *after* updating the tracker at least once.
                if (unityCollider.gameObject.isStatic)
                    ObiColliderWorld.GetInstance().MarkColliderAsNotNeedingUpdate(shapeHandle);
            }
            // If the unity collider is null but its handle is valid, the unity collider has been destroyed.
            else if (shapeHandle != null && shapeHandle.isValid)
                RemoveCollider();
        }

        private void OnEnable()
        {
            // Initialize using the source collider specified by the user (or find an appropiate one).
            FindSourceCollider();
        }

        private void OnDisable()
        {
            RemoveCollider();
        }

    }
}

